home *** CD-ROM | disk | FTP | other *** search
/ CD Ware Multimedia 1995 May / cd Ware (Juegos) Epimundo.iso / DOS / C / CJDATES.ZIP / DATES.DOC < prev    next >
Encoding:
Text File  |  1991-07-26  |  37.7 KB  |  897 lines

  1.  
  2.  
  3.  
  4.  
  5.                            CRAZY JACK'S DATE ROUTINES
  6.                                   USER'S GUIDE
  7.  
  8.                          (c)Copyright 1991 by Crazy Jack
  9.                                All Rights Reserved
  10.  
  11.                                      NOTICE
  12.  
  13.         This is NOT Public Domain software!  You don't have to pay for it
  14.         (except for distribution costs) but it IS copyrighted.   You  may
  15.         not sell or distribute the ZDay and ZDate routines or the various
  16.         header,  "include", documentation or example files for any charge
  17.         beyond  what is currently customary for "shareware" distribution,
  18.         but you may freely incorporate them into your own software  with-
  19.         out  further  charge or royalties.   In other  words,  use  Crazy
  20.         Jack's Date Routines freely, but don't steal.
  21.  
  22.         Crazy  Jack's Date Routines may only be distributed as  a  single
  23.         package containing the following 14 files:
  24.  
  25.              READ.ME        Introductory remarks and instructions.
  26.              DATES.DOC      The User's Guide (this file).
  27.              DATES.ASM      The Date Routines assembler source.
  28.              DATES.LST      The assembly listing.
  29.              DATES.OBJ      The assembled object module.
  30.              DATES.PAS      The Turbo Pascal unit source code.
  31.              DATES.TPU      The Turbo Pascal v6.0 compiled unit.
  32.              DATES.INC      Additional Turbo Pascal source code.
  33.              DATES.H        C header for date routines.
  34.              DATENAMS.C     Additional C source code for #including.
  35.              DTP.PAS        Turbo Pascal date routines demo source code.
  36.              DTP.EXE        Compiled Turbo Pascal date routines demo.
  37.              DTC.C          Borland C++ C date routines demo source code.
  38.              DTC.EXE        C date routines demo, ready-to-run.
  39.  
  40.         If you wish to make changes, go ahead, BUT:
  41.  
  42.         1.)
  43.              If  you  distribute the result,  it must be done  under  the
  44.              rules described in this notice.
  45.         2.)
  46.              Leave  the original copyright notices intact;  do not remove
  47.              existing material.
  48.         3.)
  49.              Add your own copyright notices where appropriate.
  50.         4.)
  51.              Clearly indicate where you have made changes.
  52.  
  53.         Turbo Pascal,  Borland C++ and Paradox are trademarks of  Borland
  54.         International.
  55.  
  56.  
  57.                                            1
  58.  
  59.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  60.  
  61.  
  62.         This material has no warranties, expressed or implied as to suit-
  63.         ability for any particular task.
  64.  
  65.         Let's face it,  gang, even when it's theoretically possible, it's
  66.         usually  not practical to test most software exhaustively  or  to
  67.         prove its perfection.   Further, as in the case of this software,
  68.         assumptions  are made that are not true for all possible  inputs.
  69.         For  example,  no  one knows what kind of calendar may be in  use
  70.         10000 years from now (if any),  and Gregorian dates have no mean-
  71.         ing in the period before the Gregorian calendar was adopted.  (It
  72.         was adopted at different times in different places!)  The  number
  73.         of  days between two dates always includes weekends and holidays;
  74.         you'll  have to determine the number of business  days  yourself.
  75.         And  so-on.   Only a fool will use any piece of software  without
  76.         testing  it  for suitability first.   Since you're getting  these
  77.         routines for distribution charges only,  what's your beef?   Any-
  78.         way,  I've included the source code so you can "fix" anything you
  79.         don't like (at your own risk, of course).
  80.  
  81.         The code was assembled with the Turbo Assembler v2.5 and works as
  82.         I  intended  with  programs written in Borland C++ v1.0  (ANSI  C
  83.         mode) and Turbo Pascal v6.0 (so far).
  84.  
  85.                                SO WHAT GOOD IS IT?
  86.  
  87.         For periods covered by the Gregorian calendar, these routines can
  88.         be  used  to determine the day of the week for a given  date,  to
  89.         convert between Gregorian and Julian dates, to validate Gregorian
  90.         dates (Is 2/29/1900 a valid date?   No, by the Gregorian 400-year
  91.         rule that was NOT a leap year!),  to determine the number of days
  92.         between  two dates,  and to find a date some number of days  from
  93.         another date.
  94.  
  95.         Since there are 36524 or 36525 years per century,  by subtracting
  96.         a base Day Number from the Day Numbers your dates yield,  you can
  97.         represent  a range of a bit over 179 years in a  16-bit  unsigned
  98.         integer (Turbo Pascal "word").
  99.  
  100.         By storing your dates as Day Numbers you simplify the comparison,
  101.         testing and sorting of dates.   By using the Day Number as an in-
  102.         dex  into a suitably constructed bit table you can determine if a
  103.         day is a special date such as a holiday.
  104.  
  105.         The  monotonic nature of the Day Numbers eliminates the need  for
  106.         century break testing, something that will soon be a problem.
  107.  
  108.  
  109.  
  110.                                            2
  111.  
  112.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  113.  
  114.  
  115.                                   INSTALLATION
  116.  
  117.         TURBO PASCAL:
  118.  
  119.         If you use TP v6.0, just copy DATES.TPU into your TPU library and
  120.         DATES.INC  into the library where you keep frequently  used  $In-
  121.         clude files.  (You DO have such a library, don't you?)
  122.  
  123.         Those of you with other versions will have to compile the TPU for
  124.         your version.  Place DATES.OBJ and DATES.PAS into the same subdi-
  125.         rectory on some drive (your RAM disk is a nice place).  Make this
  126.         the  default drive and subdirectory.   Execute your version of TP
  127.         and  load DATES.PAS.   Set up to compile to disk,  then  compile.
  128.         Copy  the  resulting  DATES.TPU  into  your  TPU  library.   Copy
  129.         DATES.INC into your $Includes library, and you're ready to go.
  130.  
  131.         Borland C:
  132.  
  133.         Copy DATES.OBJ into your object library where the linker can find
  134.         it.   Copy DATES.H into your header library,  and copy DATENAMS.C
  135.         into your #include library.
  136.  
  137.                               THE MECHANICS OF USE
  138.  
  139.         TURBO PASCAL:
  140.  
  141.         You'll  need  a  "uses" statement that calls out "Dates"  so  the
  142.         DATES.TPU will be accessed.   If you need the additional routines
  143.         in DATES.INC, you can use either a {$I dates.inc} statement some-
  144.         where before you use the routines.   Or copy DATES.INC into  your
  145.         source code and remove the routines you don't need, or alter them
  146.         to suit; for example, you might change day and month names to all
  147.         upper  case,  or  change the month and weekday names  to  another
  148.         language.
  149.  
  150.         Borland C:
  151.  
  152.         Put  a #include statement for "dates.h" in the front of each  mo-
  153.         dule  in which you use date routines.   If you are using DOW() or
  154.         MonthName(),  remember  to  #include or copy (and alter  to  suit
  155.         yourself) DATENAMS.C into one of the modules so the code will get
  156.         compiled somewhere.  YDay() is defined as a macro in DATES.H, and
  157.         DATES.OBJ supplies the code for ZDay() and ZDate().  You must put
  158.         an  entry  for DATES.OBJ in your project file,  or in  your  make
  159.         file's link command or response file, or wherever is needed so it
  160.         will be linked.
  161.  
  162.  
  163.                                            3
  164.  
  165.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  166.  
  167.  
  168.                               USING ZDAY AND ZDATE
  169.  
  170.         These are the routines for converting Gregorian dates to and from
  171.         Day Numbers.   They are called using the PASCAL calling sequence,
  172.         and are FAR calls.  All pointers are FAR pointers.
  173.  
  174.         They  can be used with both Borland C and Turbo Pascal  programs.
  175.         The header file DATES.H defines the interface for Borland C,  and
  176.         the  unit DATES.TPU defines it (and supplies the code) for  Turbo
  177.         Pascal.
  178.  
  179.         The  C  calling sequences are defined by these prototypes  (which
  180.         you will find, with more detail, in DATES.H):
  181.  
  182.         unsigned long int far pascal ZDay( unsigned int Year,
  183.                                   unsigned int Month, unsigned int Day );
  184.  
  185.         int far pascal ZDate(unsigned long int DayNumber,
  186.                       unsigned int far *Year, unsigned int far *Month,
  187.                                                  unsigned int far *Day );
  188.  
  189.         The  Turbo Pascal prototypes (found in DATES.PAS  and,  thus,  in
  190.         DATES.TPU) are:
  191.  
  192.         function ZDay(Year, Month, Day : word) : longint;
  193.  
  194.         function ZDate(DayNumber : longint; var Year, Month, Day : word )
  195.                                                                : boolean;
  196.  
  197.         A call to "ZDay" uses 14 bytes of space on the stack;  a call  to
  198.         "ZDate" eats 26.  The routines occupy 354 bytes of code space and
  199.         no data space.  For assembly language coders, the contents of the
  200.         AX,  BX,  CX and DX registers are destroyed;  all others are pre-
  201.         served.  The condition flags in the flag register are altered.
  202.  
  203.         ZDAY:
  204.  
  205.         ZDay  returns a 32-bit unsigned integer representing the Day Num-
  206.         ber  calculated from the supplied Gregorian date.   Although  the
  207.         Pascal call defines it as a longint,  you will not have to  worry
  208.         about getting a negative number back.
  209.  
  210.         If the resulting Day Number is zero,  you have supplied a Gregor-
  211.         ian  date  that  is too early or too far in the  future  for  the
  212.         calculations  to  be  performed  correctly  with  this  routine's
  213.         methods.
  214.  
  215.  
  216.                                            4
  217.  
  218.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  219.  
  220.  
  221.         The Year,  Month and Day parameters are all unsigned 16-bit inte-
  222.         gers,   Year  must be the FULL YEAR.   1902 must be  supplied  as
  223.         1902, not 02.  It must be in the range 1 to 25599 or zero will be
  224.         returned.   Month  and  Day must be in the range  0-65535.   This
  225.         should not be too restrictive for most purposes.
  226.  
  227.         Note that Day,  Month and Year are all UNSIGNED.  Negative values
  228.         are  unacceptable in these variables.   This is also true of  the
  229.         any  Day  Number you give to ZDate!   Negative numbers look  like
  230.         large positive numbers and will give Wrong Results!
  231.  
  232.         ZDATE:
  233.  
  234.         The  same  parameters are used with ZDate.   Just supply the  Day
  235.         Number,  and ZDate fills in the Year, Month and Day for you.  The
  236.         supplied  Day  Number should be greater than 121  and  less  than
  237.         23920640.   For  Day Numbers outside this range,  ZDate returns a
  238.         Gregorian  date of 0-0-0 and a return value of 0 (FALSE  in  both
  239.         Pascal  and C).   A good conversion returns 1 (TRUE in both C and
  240.         Pascal).
  241.  
  242.         NOTE:   ZDay can convert certain dates into Day Numbers that  are
  243.         outside  the  range that ZDate can convert  back.   These  dates,
  244.         however,  involve years that are either zero or very, very large,
  245.         and  are well outside the range of dates that we are likely to be
  246.         interested in.
  247.  
  248.                                     IMPORTANT
  249.  
  250.         To avoid trouble, remember:
  251.  
  252.         1.)
  253.              All the numeric values are UNSIGNED!
  254.  
  255.         2.)
  256.              The year MUST be given IN FULL!   7/4/91 means July 4,  091,
  257.              NOT July 4, 1991!
  258.  
  259.         3.)
  260.              Check  the Day Number you get back from "ZDay".   If  it  is
  261.              ZERO, the date you gave it was WAY out of range, and probab-
  262.              ly nonsense!
  263.  
  264.                        PARADOX (AND PARADOX ENGINE) USERS
  265.  
  266.         Paradox  stores its dates as long integer Day  Numbers.   Paradox
  267.         Day  Numbers  are  always 427 LESS than ZDay/ZDate  Day  Numbers.
  268.         Paradox Day Numbers MOD 7 (like "ZDay" Day Numbers MOD 7)  return
  269.         the day of the week.
  270.  
  271.  
  272.                                            5
  273.  
  274.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  275.  
  276.  
  277.                   OKAY, WHAT DO I DO WITH THESE DATE ROUTINES?
  278.  
  279.         The Day Number is related to the Julian Day Number (the number of
  280.         days since some date in the distant past),  but is not the  same.
  281.         It  is valid only for the years since the Gregorian calendar  was
  282.         introduced, whenever that was in your area.
  283.  
  284.         You  can  use the Day Numbers of two dates to find the number  of
  285.         days between them.
  286.  
  287.         The  remainder resulting when the Day Number is divided by  7  is
  288.         the  day of the week,  0 meaning Sunday thru 6 meaning  Saturday.
  289.         This  conforms with the day-of-the-week number returned by MS-DOS
  290.         and other software.
  291.  
  292.         In files,  store the Day Number instead of the date.  To conserve
  293.         space you can subtract a base day number and save the difference.
  294.         Careful  choice of the base Day Number will let you use a  16-bit
  295.         unsigned integer for holding the difference.  The 65536 days that
  296.         16 bits holds represent more than 179 years.   The monotonic  na-
  297.         ture  of  the Day Numbers means you don't need tricks to  compare
  298.         the dates or to sort on them, and you don't have to watch out for
  299.         the  turn of the century.   None of the values represent  invalid
  300.         dates,  unlike  the method used by DOS (and a lot of other  soft-
  301.         ware) to store dates in 16 bits!
  302.  
  303.         All  dates,  valid or not,  convert to Day Numbers of some  kind.
  304.         All in-range Day Numbers convert to valid Gregorian dates.  So if
  305.         you  convert  a Gregorian date to a Day Number and back,  if  the
  306.         resulting Gregorian date doesn't match the original, the original
  307.         is invalid.
  308.  
  309.         For  example,  if you convert 2/29/1900 to a Day Number  you  get
  310.         694082.   Convert  it back and you get 3/1/1900,  so 2/29/1900 is
  311.         not a valid date.  And that's true.  Even though it looks like it
  312.         should be a leap year, it isn't  The 400-year Gregorian rule says
  313.         that  the  leap year is dropped on the century year  unless  that
  314.         century  year can be divided by 400 without a remainder.   If  we
  315.         convert 2/29/2000 to a Day Number we get 730606,  which  converts
  316.         back to 2/29/2000, so that IS a valid date.
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.                                            6
  326.  
  327.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  328.  
  329.  
  330.         Note that the Day Number tends to be what an invalid date "should
  331.         have been".   That is,  in the Feb 29,  1900 case shown above, it
  332.         gave the Day Number of March 1,  1900.   We can take advantage of
  333.         this to find the last day of a month.   Begin with the  Gregorian
  334.         date.   Add 1 to the month (even to December),  set the Day to 0,
  335.         convert to a Day Number, then back to Gregorian.
  336.  
  337.         To  find  the Julian day of the year,  just take  the  difference
  338.         between the Day Number of the desired date and the Day Number  of
  339.         January ZERO (so Jan 1 will come out day 1) of the same year.   A
  340.         C  macro is defined in DATES.H,  and a Pascal function is defined
  341.         in DATES.INC,  under the name "YDay", to calculate the Julian day
  342.         of the year.
  343.  
  344.         To convert a Julian date to a Gregorian date,  give month 1,  the
  345.         Julian  day of the year,  and the year to ZDay,  and convert  the
  346.         resulting Day Number back to a Gregorian date.
  347.  
  348.         To find the date of next Friday,  take the Day Number of  today's
  349.         date MOD 7 (today's day of the week).  Subtract this from today's
  350.         Day  Number,  which gives the Day Number of last Sunday.   If to-
  351.         day's day of the week is not than 5 (Friday hasn't come yet), add
  352.         5  to last Sunday's Day Number,  otherwise add 12 (tacking on  an
  353.         extra week).   This gives the Day Number of NEXT Friday,  even if
  354.         TODAY  is Friday.   You can convert the Day Number back to a Gre-
  355.         gorian date,  or save the Day Number for testing.   Your  program
  356.         can  thus  determine  when next Friday has come (or  passed)  for
  357.         doing Friday Processing, at which time you can calculate the NEXT
  358.         Friday Day Number +to continue the cycle.
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.                                            7
  379.  
  380.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  381.  
  382.  
  383.                                      EXTRAS
  384.  
  385.         DATES.PAS:
  386.  
  387.         This  is  the source code for DATES.TPU.   It uses the  universal
  388.         date calculation routines in DATES.OBJ,  which have been designed
  389.         to  work with both Pascal and C,  so programs written  in  either
  390.         language will produce identical Day Numbers.
  391.  
  392.         DATES.INC:
  393.  
  394.         This  is  Turbo Pascal code that defines YDay (the Julian day  of
  395.         the year function),  DOW (a function that returns the day of  the
  396.         week  spelled  out in a string),  and MonthName (a function  that
  397.         returns the name of the month spelled out in a string).
  398.  
  399.         You  can  use it as is,  just providing a {$I DATES.INC} in  your
  400.         code,  or  you  can copy it into your code with your  editor  and
  401.         remove what you're not using and modify the rest.  You might, for
  402.         instance,  change the weekday and month names to all caps or ano-
  403.         ther language.   DOW and MonthName protect themselves by  masking
  404.         the  month or day of the week to small ranges (to keep them  from
  405.         going  outside  their tables).   You can change this  to  modular
  406.         division  or  range checking if you so desire;   you've  got  the
  407.         source code.
  408.  
  409.         Note that the strings returned by DOW and MonthName are specially
  410.         typed instead of being generic STRINGs.   Generic STRINGs eat 255
  411.         bytes apiece somewhere in storage in temporary space provided  by
  412.         Turbo Pascal; there's no sense in wasting all that space.
  413.  
  414.         DATES.H:
  415.  
  416.         This  header file provides the ZDay() and ZDate() prototypes  for
  417.         ANSI  C  users.   It also has prototypes for  YDay(),  DOW()  and
  418.         MonthName(),  and it defines YDay() as a macro.   The actual code
  419.         for DOW() and MonthName() are in DATENAMS.C.
  420.  
  421.         DATENAMS.C:
  422.  
  423.         This is a file to be #included,  or copied into and modified,  in
  424.         one  of  the modules of your C program to provide  the  functions
  425.         DOW() and MonthName() defined in DATES.H.   If you don't use 'em,
  426.         you don't include 'em.   Since there are no NEAR or FAR modifiers
  427.         in any of the declarations, they compile to YOUR memory model.
  428.  
  429.  
  430.  
  431.                                            8
  432.  
  433.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  434.  
  435.  
  436.                                     THE DEMOS
  437.  
  438.         Demo programs are provided in both C (DTC) and Turbo Pascal (DTP)
  439.         to illustrate some of the ways in which the date routines can  be
  440.         used.  They are also handy for doing manual date calculations.
  441.  
  442.         Both  demo programs work the same way.   Execute either from  the
  443.         command  line (no parameters are used) and it will ask  "Gimme  a
  444.         date: ".
  445.  
  446.         You can type in a single Day Number, a Julian date (ddd/yyyy), or
  447.         a Gregorian date (mm/dd/yyyy), then hit ENTER.
  448.  
  449.         IMPORTANT-->   All digits of the year must be given;  if you mean
  450.         1991, you must specify "1991", NOT "91".
  451.  
  452.         The  program  will then show you various conversion  results  and
  453.         return to the "Gimme a date: " prompt.
  454.  
  455.         To exit, just hit ENTER from the prompt.
  456.  
  457.         I have included the source code for both to serve as usage  exam-
  458.         ples.
  459.  
  460.         If  you  compile the C version you'll get some warnings,  all  of
  461.         which are meaningless.   For some reason,  Borland's C  compilers
  462.         don't know that a "do" can ALWAYS be reached from its "while"!
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.  
  470.  
  471.  
  472.  
  473.  
  474.  
  475.  
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.                                            9
  485.  
  486.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  487.  
  488.  
  489.                                  TECHNICAL STUFF
  490.  
  491.         There are a number of descriptions of Day Number routines  float-
  492.         ing around.   The one I use came from a routine I saw for the HP-
  493.         65  programmable pocket calculator to find the Julian Day Number.
  494.         To  it  I added the adjustments for the 400-day  Gregorian  rule.
  495.         For  the  coming  turn of the century we don't need it since  the
  496.         year 2000 divides by 400 without a remainder and is, thus, a leap
  497.         year;  but programmers can be picky about details, so -- what the
  498.         heck!
  499.  
  500.         So far,  I have written this routine in HP-65, COBOL, Easytrieve,
  501.         BASIC and Pascal.  Those implementations are all slow and ineffi-
  502.         cient.   Since  I now need a date routine at work that I can  use
  503.         with  assembler,  Pascal and C on our PC-type machines I figure I
  504.         may as well do a nice small one in assembler for the 80x86 and be
  505.         done with it.
  506.  
  507.         To calculate the Day Number from a Gregorian date we first adjust
  508.         the  year and month so the month values run from 4  to  15,  with
  509.         March being 4 and February being 15:
  510.  
  511.               if Month < 3
  512.               then
  513.                   Add 13 to Month
  514.                   Subtract 1 from Year
  515.               otherwise
  516.                   Add 1 to Month.
  517.  
  518.         Now comes the meat of the calculation:
  519.  
  520.               Day Number =   Day of the month
  521.                            + The Integer Part of (Month * 30.6001)
  522.                            + The Integer Part of (Year * 365.25)
  523.                            - The Integer Part of (3/4 of
  524.                                          The Integer Part of (Year / 400)
  525.                                                  ).
  526.  
  527.         (That  last mess accounts for the Gregorian 400-year rule.)   Now
  528.         if we divide this by 7 (to find the day of the week) we get 0 for
  529.         Saturday.   For  a variety of reasons,  mostly related to  common
  530.         practice,  I decided to adjust this further by subtracting 1 from
  531.         it  so Sunday becomes 0.
  532.  
  533.         If you want to duplicate Paradox Day Numbers,  you can change the
  534.         code in ZDAY to subtract 428 instaed of 1, and you'll have to add
  535.  
  536.  
  537.                                           10
  538.  
  539.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  540.  
  541.  
  542.         a  check  for a possible borrow in case of underflow  to  a  not-
  543.         allowed negative value,   You'll also have to modify ZDATE to get
  544.         the 428 added back.
  545.  
  546.         Converting back to a Gregorian date is not so easy.
  547.  
  548.         First we back out the Gregorian 400-year rule:
  549.  
  550.               Divide Day Number less 121 by 146097:
  551.                      Q = the quotient
  552.                      R = the remainder.
  553.  
  554.               if R is not 0
  555.               then
  556.                   Add the quotient from ( (R -1) / 36524 ) to Q.
  557.  
  558.               N = Day Number plus 1 plus Q.
  559.  
  560.         Note that 145097 is the number of days in 400 years with the 400-
  561.         year  rule applied,  and 36524 is the number of days in a century
  562.         in  which  the century year is NOT a leap  year.   36525  is  the
  563.         number  of  days  in a century where the century year IS  a  leap
  564.         year.   Once the 400-year rule is backed out, we have ALL centur-
  565.         ies containing 36525 days,  which simplifies the remaining calcu-
  566.         lations.
  567.  
  568.         We  extract the Gregorian date from the adjusted Day Number  "N".
  569.         First we get the tentative year:
  570.  
  571.               Year = The Integer Part of ( (N - 122.1) / 365.25 ).
  572.  
  573.         --and  we remove the tentative year's worth of days from the  ad-
  574.         justed Day Number "N":
  575.  
  576.               N = N - the Integer Part of (Year * 365.25).
  577.  
  578.         From this we extract the tentative month:
  579.  
  580.               Month = The Integer Part of (N / 30.6001).
  581.  
  582.         The  current day is what is left over when we remove the days due
  583.         to the months:
  584.  
  585.               Day = N - The Integer Part of (Month * 30.6001).
  586.  
  587.  
  588.  
  589.  
  590.                                           11
  591.  
  592.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  593.  
  594.  
  595.         Finally we adjust the Year and the Month:
  596.  
  597.               if Month > 13
  598.               then
  599.                   Subtract 13 from Month
  600.                   Add 1 to the Year
  601.               otherwise
  602.                   Subtract 1 from the Month.
  603.  
  604.         --and the deed is done.
  605.  
  606.         Since  I  intend that this set of routines be usable  on  all  PC
  607.         compatible systems,  I can't assume the availability of a numeric
  608.         coprocessor or 32-bit arithmetic. So everything is done in 16-bit
  609.         integer  arithmetic  using the CPU registers (no  coprocessor  or
  610.         extended  registers) and 8086 instructions only.   In some places
  611.         it makes the code a little awkward, but it gives its best perfor-
  612.         mance on the lowliest of PCs where it's needed the most.
  613.  
  614.         In  a couple of places I had to divide by a number more  than  16
  615.         bits long, once in ZDAY, when I had to divide by 146097, and once
  616.         in ZDATE where I had to divide by 306001,   Fortunately,  both of
  617.         these numbers can be factored into values that fit 16 bits.
  618.  
  619.         Let the division be:  A / (B * C),  where B and C are the factors
  620.         of  the original divisor.   We are looking for the quotient Q and
  621.         the remainder R.
  622.  
  623.              Divide A by B:
  624.                   Q1 = the quotient
  625.                   R1 = the remainder;
  626.  
  627.              Divide Q1 by C:
  628.                   Q2 = the quotient
  629.                   R2 = the remainder;
  630.  
  631.              ---then:
  632.                   the quotient Q = Q2
  633.                   the remainder R = R1 + (B * R2).
  634.  
  635.         Divide by the largest factor first to allow the largest dividend.
  636.  
  637.         For  those of you who haven't messed around with integer multiply
  638.         and divide as machines generally perform them,  they are done the
  639.         way the old mechanical desk calculators did it:  by repeated adds
  640.         (multiplication) or subtracts (division) and shifts.
  641.  
  642.  
  643.                                           12
  644.  
  645.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  646.  
  647.  
  648.         In  division,  the dividend is always two units (words or  bytes)
  649.         long,  and  the divisor,  quotient and remainder are all one unit
  650.         long.   To  avoid divide overflow,  the value in  the  high-order
  651.         (leftmost)  unit  of the dividend must be less than the  divisor.
  652.         You will see a number of tests for this in the code.
  653.  
  654.         Likewise,  the result of multiplying two single unit numbers is a
  655.         double unit number.  Multiplying two 16-bit numbers returns a 32-
  656.         bit product. There is no possibility of overflow.
  657.  
  658.         I  take  advantage of these built-in "type conversions"  wherever
  659.         possible.   I  also utilize the ability to get both the  quotient
  660.         and the remainder from a single divide,  something that few high-
  661.         level languages (outside of COBOL) support.
  662.  
  663.         I chose the Pascal calling sequence and use no calls to  run-time
  664.         library routines so I can use the same code (DATES.OBJ) with both
  665.         C and Pascal programs.
  666.  
  667.         If you don't mind shoving stuff on the stack and the other  fool-
  668.         ing around required,  you can use the routines with assembly code
  669.         as well.
  670.  
  671.         To call ZDAY,  push,  in this order: the 16-bit unsigned integers
  672.         specifying the year,  the month and the day onto the stack,  then
  673.         execute a FAR CALL to ZDAY.  The resulting Day Number is returned
  674.         in the DX:AX register pair.
  675.  
  676.         To call ZDATE, push, in this order: a 32-bit word holding the Day
  677.         Number  to be converted (in the usual byte-reversed  order),  and
  678.         FAR  POINTERs to the three 16-bit words to receive the year,  the
  679.         month and the day.   Then make a FAR CALL to ZDATE.  Upon return,
  680.         the  Gregorian  date will be in the year,  month  and  day  words
  681.         pointed to by those addresses you pushed on the stack, and the AX
  682.         register  will contain 1 if everything went okay,  or zero if the
  683.         Day Number was out of range (testing anything but the AL would be
  684.         wasteful).
  685.  
  686.         Specifying the LARGE model to the assembler and explicitly defin-
  687.         ing  all the calls and pointers as PASCAL FAR in "dates.h" allows
  688.         the  proper calls and pointers to be compiled and proper  linking
  689.         to occur with all the C memory models.   For example, the program
  690.         DTC.EXE was compiled as a TINY model program and works just fine.
  691.  
  692.  
  693.  
  694.  
  695.  
  696.                                           13
  697.  
  698.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  699.  
  700.  
  701.                                    PERFORMANCE
  702.  
  703.         There  would  be  no sense in going to all this  trouble  if  the
  704.         resulting code was neither smaller nor faster than code  produced
  705.         by a good compiler.   There may be no sense in it anyway,  but it
  706.         was fun.
  707.  
  708.         Is there an improvement?
  709.  
  710.         354 bytes is not a lot of space to take up, and stack use is held
  711.         down, but this is no big deal.
  712.  
  713.         The  most convenient alternative routines I can find for a  speed
  714.         comparison  is  the  set of date "encode" and  "decode"  routines
  715.         found in the Borland Paradox Engine.  I don't mean any offense to
  716.         Borland with this test, it's just what I have at hand.  I presume
  717.         their routines are coded in C using double precision  arithmetic.
  718.         The results would be more general-purpose and portable,  but less
  719.         efficient.
  720.  
  721.         In my routines I don't need general-purpose methods.  I have been
  722.         able  to  factor  into 16-bit values the numbers that I  need  to
  723.         divide  by,  so I can use integer multiplies  and  divides.   The
  724.         performance differences on different processors are interesting.
  725.  
  726.         I  concocted a test program (in Turbo Pascal,  if you must  know)
  727.         that  allows me to enter a loop count.   It then runs  a  counted
  728.         series  of  Day Numbers (starting in the  current century)  first
  729.         through  the conversion to Gregorian date,  then back to the  Day
  730.         Number,  using the Paradox Engine routines.   The program  pauses
  731.         before starting, then again at the end so I can run my stopwatch.
  732.         The  same  series  of  Day Numbers  is  processed  again  through
  733.         ZDate/ZDay.   Compared to the calculation time, the loop and cal-
  734.         ling sequences are not too important.
  735.  
  736.         No,  I didn't use precision timing:  I'm not interested in minute
  737.         improvements.
  738.  
  739.         The  first system I tried it on was an old IBM XT running at 4.77
  740.         mHz  with an 8088 processor.   It took around 55 seconds for  the
  741.         Paradox  Engine routines to get through 5,000 iterations  of  the
  742.         loop.
  743.  
  744.         It took ZDate/ZDay about 5 seconds to do the job.
  745.  
  746.  
  747.  
  748.  
  749.                                           14
  750.  
  751.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  752.  
  753.  
  754.         I then moved to an IBM AT running at 8 mHz.   Here I set the loop
  755.         count to 20,000.   The PXEngine routines took about a minute, and
  756.         ZDate/Zday around 4 seconds.
  757.  
  758.         On  a 15 mHz 80386 system the times were about the same as the AT
  759.         when the loop count was set to 40,000.
  760.  
  761.         The machine I use at home is an 8 mHz XT clone with an NEC V20 in
  762.         place  of the 8088,  with the RAM refresh rate reduced  somewhat.
  763.         Central Point Software's PCSHELL says it runs at around 195% of a
  764.         PC.   It took 58 second time to do 10,000 iterations of the Para-
  765.         dox  Engine loop.   The microcoding improvements of the  multiply
  766.         and  divide  instructions  in the V20 show up in  the  Zdate/ZDay
  767.         loop: 2 seconds.
  768.  
  769.         So  was it worth it?   I doubt that the  performance  improvement
  770.         will  save enough time over the years to cover the time and  cost
  771.         of  writing  it,  let alone the additional cost (in MY  time)  of
  772.         doing this writeup.  Calculating dates is not, after all, a large
  773.         part  of any program.   But it was fun,  and the routine IS small
  774.         and fast, and I have lots of uses for it.  I'm satisfied.
  775.  
  776.  
  777.                                   MODIFICATIONS
  778.  
  779.         Those of you who are into this sort of thing and don't care about
  780.         the Gregorian 400-day rule (okay for the coming turn of the  cen-
  781.         tury) may wish to reduce the size and increase the speed of these
  782.         routines  by  removing the 400-day rule code.   Just remember  to
  783.         watch  the register contents.   I went to some trouble to try  to
  784.         have things wind up in registers where I wanted them, and pulling
  785.         code may not leave things the way the other code expects them  to
  786.         be.  If you make changes, you're on your own.
  787.  
  788.  
  789.                              OTHER COMPILERS AND C++
  790.  
  791.         While I haven't bothered to try it, I presume that ZDay and ZDate
  792.         will  work correctly with Borland C++ (C++ mode) since the use of
  793.         the Pascal calling sequence avoids name mangling problems.
  794.  
  795.         For  use with other C compilers you may have to alter the segment
  796.         and/or group names.   The use of the Pascal calling sequence  amy
  797.         create  problems with other C compilers.   I don't use other com-
  798.         pilers, so I don't know.  Since you have the source code, you can
  799.         play around with it if you need to.
  800.  
  801.  
  802.                                           15
  803.  
  804.         CRAZY JACK'S DATE ROUTINES                           USER'S GUIDE
  805.  
  806.  
  807.                                 GETTING IN TOUCH
  808.  
  809.         Everything you need should be right here in this package,  but if
  810.         you  feel the need to make comments about these routines,  or  to
  811.         ask questions, you can drop me a line at:
  812.  
  813.                   ATTN: Crazy Jack
  814.                   The Northern Illinois Klugewerks
  815.                   P.O. Box 7284
  816.                   Buffalo Grove,  IL 60089
  817.  
  818.         If you expect a reply, you MUST enclose a stamped, self-addressed
  819.         envelope.   I will try to answer, but, like most of you, I have a
  820.         "day" job that eats the bulk of my time,  including evenings  and
  821.         parts of weekends.
  822.  
  823.         If  you have improvements to the code,  please send  them  along.
  824.         But  remember,  the code must be optimized for the 8088,  not the
  825.         bigger,  faster chips.  Also, if you think you've found a mistake
  826.         in my code,  look carefully.   I may have taken advantage of side
  827.         effects that let me skip tests or use byte instead of word  oper-
  828.         ations.   In  other cases I have taken extra code space to reduce
  829.         the amount of pipeline dumping due to jumps.
  830.  
  831.         Alterations which reduce the size of the code and/or increase its
  832.         speed (without sacrificing function) are especially welcome.   In
  833.         particular,  I'd  like a cleaner method for backing out the  400-
  834.         year Gregorian rule.   No, removing it altogether does NOT quali-
  835.         fy!
  836.  
  837.  
  838.  
  839.  
  840.  
  841.  
  842.  
  843.  
  844.  
  845.  
  846.  
  847.  
  848.  
  849.  
  850.  
  851.  
  852.  
  853.  
  854.  
  855.                                           16
  856.  
  857.  
  858.          ----------------end-of-author's-documentation---------------
  859.  
  860.                          Software Library Information:
  861.  
  862.                     This disk copy provided as a service of
  863.  
  864.                            Public (software) Library
  865.  
  866.          We are not the authors of this program, nor are we associated
  867.          with the author in any way other than as a distributor of the
  868.          program in accordance with the author's terms of distribution.
  869.  
  870.          Please direct shareware payments and specific questions about
  871.          this program to the author of the program, whose name appears
  872.          elsewhere in  this documentation. If you have trouble getting
  873.          in touch with the author,  we will do whatever we can to help
  874.          you with your questions. All programs have been tested and do
  875.          run.  To report problems,  please use the form that is in the
  876.          file PROBLEM.DOC on many of our disks or in other written for-
  877.          mat with screen printouts, if possible.  PsL cannot debug pro-
  878.          programs over the telephone, though we can answer questions.
  879.  
  880.          Disks in the PsL are updated  monthly,  so if you did not get
  881.          this disk directly from the PsL, you should be aware that the
  882.          files in this set may no longer be the current versions. Also,
  883.          if you got this disk from another vendor and are having prob-
  884.          lems,  be aware that  some files may have become corrupted or
  885.          lost by that vendor. Get a current, working disk from PsL.
  886.  
  887.          For a copy of the latest monthly software library newsletter
  888.          and a list of the 3,000+ disks in the library, call or write
  889.  
  890.                            Public (software) Library
  891.                                P.O.Box 35705 - F
  892.                             Houston, TX 77235-5705
  893.  
  894.                                 1-800-2424-PSL
  895.                              MC/Visa/AmEx/Discover
  896.  
  897.                           Outside of U.S. or in Texas
  898.                           or for general information,
  899.                               Call 1-713-524-6394
  900.  
  901.                           PsL also has an outstanding
  902.                           catalog for the Macintosh.
  903.  
  904.